package com.bruce.downloader.download;

import com.bruce.downloader.constant.Constant;
import com.bruce.downloader.dto.MultipleDownInfoDto;
import com.bruce.downloader.handle.FileResponseExtractor;
import com.bruce.downloader.thread.MultipleDownThread;
import com.bruce.downloader.util.HttpClientUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.RestTemplate;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 多线程下载器
 * <br>CreateDate  2021/09/16 10:57:26
 *
 * @author luotuan
 * @version 1.0.0
 */
public class MultipleDownloader extends AbstractDownloader {
    private static ExecutorService executorService;

    public MultipleDownloader(RestTemplate restTemplate) {
        super(restTemplate);
        init();
    }

    public MultipleDownloader() {
        super(HttpClientUtils.getRestTemplate());
        init();
    }

    private void init() {
        executorService = Executors.newFixedThreadPool(Constant.THREAD_NUM);
    }

    @Override
    protected void doDownload(String decodeUrl, String dirPath, String fileName, HttpHeaders headers) {
        //List<Future<File> futureList = handleOne(decodeUrl,dirPath,fileName,headers);
        List<CompletableFuture<File>> futureList = handleTwo(decodeUrl,dirPath,fileName,headers);
        try {
            // 合并文件
            String tmpFilePath = dirPath + File.separator + fileName + ".download";
            File file = new File(tmpFilePath);
            FileChannel outChannel = new FileOutputStream(file).getChannel();
            futureList.forEach(fileFuture -> {
                try {
                    File tmpFile = fileFuture.get();
                    FileChannel channel = new FileInputStream(tmpFile).getChannel();
                    // 合并
                    outChannel.transferFrom(channel, outChannel.size(), channel.size());
                    channel.close();
                    tmpFile.delete();
                } catch (InterruptedException | ExecutionException | IOException e) {
                    e.printStackTrace();
                }
            });
            outChannel.close();
            file.renameTo(new File(dirPath + File.separator + fileName));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            scheduledExecutorService.shutdown();
            executorService.shutdown();
        }

    }

    private List<Future<File>> handleOne(String decodeUrl, String dirPath, String fileName, HttpHeaders headers){
        long eachLength = headers.getContentLength() / Constant.THREAD_NUM;
        CountDownLatch countDownLatch = new CountDownLatch(Constant.THREAD_NUM);
        List<Future<File>> futureList = new ArrayList<>();
        for (int i = 0; i < Constant.THREAD_NUM; i++) {
            MultipleDownInfoDto infoDto = MultipleDownInfoDto.builder().setRestTemplate(getRestTemplate()).setFileName(fileName)
                    .setDirFilePath(dirPath).setUrl(decodeUrl).setEachLength(eachLength).setIndex(i).build();
            MultipleDownThread downThread = new MultipleDownThread(infoDto, countDownLatch);
            futureList.add(executorService.submit(downThread));
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return futureList;
    }

    private List<CompletableFuture<File>> handleTwo(String decodeUrl, String dirPath, String fileName, HttpHeaders headers){
        long contentLength = headers.getContentLength();

        //均分文件的大小
        long step = contentLength / Constant.THREAD_NUM;

        List<CompletableFuture<File>> futures = new ArrayList<>();
        for (int index = 0; index < Constant.THREAD_NUM; index++) {
            //计算出每个线程的下载开始位置和结束位置
            String start = step * index + "";
            String end = index == Constant.THREAD_NUM - 1 ? "" : (step * (index + 1) - 1) + "";

            String tempFilePath = dirPath + File.separator + "." + fileName + ".download." + index;
            FileResponseExtractor extractor = new FileResponseExtractor(tempFilePath,index);

            CompletableFuture<File> future = CompletableFuture.supplyAsync(() -> {
                RequestCallback callback = request -> {
                    //设置HTTP请求头Range信息，开始下载到临时文件
                    request.getHeaders().add(HttpHeaders.RANGE, "bytes=" + start + "-" + end);
                };
                return restTemplate.execute(decodeUrl, HttpMethod.GET, callback, extractor);
            }, executorService).exceptionally(e -> {
                e.printStackTrace();
                return null;
            });
            futures.add(future);
        }
        return futures;
    }
}
